// c++ standard lib
#include <iostream>
#include <cerrno>
#include <cstring>
#include <pthread.h>

// Linux c++ socket lib
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>

// head files
#include "UDPClient.h"

using namespace std;

void ClearPtr(char* ptr) 
{ // clear array cache space
    delete [] ptr;
    ptr = 0;
}

void UDPClient::Error(const char* message)
{
    cout << message << strerror(errno) << "(errno: " << errno << ")" << endl;
	close(udp_fd);
	exit(-1);
}

int UDPClient::InitUDPClient()
{
	/* init udp client socket */

	// @PF_INET: Protocol Family
	// @SOCK_DGRAM: UDP/IP
	if ((udp_fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
	{
        cout << "Client create udp socket error" << strerror(errno)
            << "(errno: )" << errno << ")"
            << endl;
		exit(-1);
	}

    // set content in serveraddr struct to 0
	bzero(&udp_client_addr, sizeof(udp_client_addr));

	udp_client_addr.sin_family = AF_INET; // @AF_INET: 
	udp_client_addr.sin_addr.s_addr = htonl(INADDR_ANY); // receive packets from any ip
	udp_client_addr.sin_port = htons(udpPort); // bind port

    if ((bind(udp_fd, (struct sockaddr*)&udp_client_addr, sizeof(sockaddr)) == -1))
	{
		Error("UDP Client bind socket error: ");
	}

    char clientHost[256];
    gethostname(clientHost, 256);
    hostent* pHost = gethostbyname(clientHost);
    in_addr addr;
    for (int i = 0; ; i++)
    {
        char *p = pHost->h_addr_list[i];
        if (p == nullptr) break;

        memcpy(&addr.s_addr, p, pHost->h_length);
        printf("bind to local address -> %s:%d \n", inet_ntoa(addr), udpPort);
    }

    bzero(&peer, sizeof(peer));
    for (int i = 0; i < MAX_ADDR_NUMBER - 1; i++)
    {
        char *p = pHost->h_addr_list[i];
        if (p == nullptr) break;
        memcpy(&peer.addr[i].ip, &p, pHost->h_length);
        peer.addr[i].port = udpPort;
        peer.addrNum ++;
    }


    cout << "Start P2P client..." << endl;
    int ret = pthread_create(&recv_tid, NULL, RecvMsg, (void*)this);
    if (ret != 0) {
        Error("p2p client recv error: ");
    }
    return 0;
}

bool UDPClient::Login(char* username, char* server_ip)
{
    if (login || strlen(username) > MAX_USERNAME - 1) { return false; }

    udp_server_ip = inet_addr(server_ip);
    strncpy(peer.username, username, strlen(username));

    // server name
    sockaddr_in serverAddr = { 0 };
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = udp_server_ip;
    serverAddr.sin_port = htons(UDP_SERVER_PORT);

    // send current peer info to server
    Message loginMsg;
    loginMsg.type = USERLOGIN;
    memcpy(&loginMsg.peer, &peer, sizeof(PEER_INFO));

    for (int i = 0; i < MAX_TRY_NUMBER; i++) 
    {
        sendto(udp_fd, (char*)&loginMsg, sizeof(loginMsg), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));
        for (int j = 0; j < 10; j++)
        {
            if (login) {
                return true;
            }
            usleep(300000);
        }
    }
    return false;
}

void UDPClient::Logout()
{
    if (login)
    {
        // send logout info to server
        Message logoutMsg;
        logoutMsg.type = USERLOGOUT;
        memcpy(&logoutMsg.peer, &peer, sizeof(PEER_INFO));

        sockaddr_in serverAddr = { 0 };
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = udp_server_ip;
        serverAddr.sin_port = htons(UDP_SERVER_PORT);

        sendto(udp_fd, (char*)&logoutMsg, sizeof(logoutMsg), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));
        login = false;
    }
}

bool UDPClient::GetPeerList()
{
    // server address
    sockaddr_in serverAddr = { 0 };
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = udp_server_ip;
    serverAddr.sin_port = htons(UDP_SERVER_PORT);

    // construct packet
    Message msgList;
    msgList.type = GETPEERLIST;
    memcpy(&msgList.peer, &peer, sizeof(PEER_INFO));

    // delete all nodes
    pthread_mutex_lock(&peerListLock);
    this->peerList->DeleteAllPeers();
    pthread_mutex_unlock(&peerListLock);

    // send GETPEERLIST request, wait for list transmission
    userList = false;
    int userCount = 0;
    for (int i = 0; i < MAX_TRY_NUMBER; i++)
    {
        sendto(udp_fd, (char*)&msgList, sizeof(msgList), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));
        for (int j = 0; j < 10; j++)
        {
            if (userList) {
                return true;
            }
            usleep(300000);
        }
    }
    return false;
}

bool UDPClient::SendText(char* username, char* text, int text_len)
{
    if (!login || strlen(username) > MAX_USERNAME - 1 || text_len > MAX_PACKET_SIZE - sizeof(Message))
    {
        return false;
    }

    // construct packet
    char sendBuf[MAX_PACKET_SIZE];
    Message* msg = (Message*)sendBuf;
    memcpy(&msg->peer, &peer, sizeof(peer));
    memcpy(msg->text, text, text_len);
    msg->text[text_len] = '\0';
    printf("send msg !!!!!!!!!!!!!!!1111 msg+ 1: %s\n", msg->text);

    msgAck = false;
    for (int i = 0; i < MAX_TRY_NUMBER; i++)
    {
        pthread_mutex_lock(&peerListLock);
        PEER_INFO* peerInfo = this->peerList->GetPeer(username);
        pthread_mutex_unlock(&peerListLock);

        if (peerInfo == NULL) { return false; }

        // 如果对方P2P地址不为0，就试图以它为目的地址发送数据，
		// 如果发送失败，则认为此P2P地址无效

        if (peerInfo->p2pAddr.ip != 0)
        {
            msg->type = P2PMESSAGE;

            sockaddr_in peerAddr = { 0 };
            peerAddr.sin_family = AF_INET;
            peerAddr.sin_addr.s_addr = peerInfo->p2pAddr.ip;
            peerAddr.sin_port = htons(peerInfo->p2pAddr.port);

            printf("send p2p msg to %s:%d \n", inet_ntoa(peerAddr.sin_addr), ntohs(peerAddr.sin_port));

            sendto(udp_fd, (char*)msg, text_len + sizeof(Message), 0,
                (sockaddr*)&peerAddr, sizeof(peerAddr));


            for (int j = 0; j < 10; j++)
            {
                if (msgAck) { return true; }
                usleep(300000);
            }
        }

        // construct packet
        char tempBuf[sizeof(Message) + MAX_USERNAME];
        Message* p = (Message*)tempBuf;
        p->type = REQUESTPUNCH;
        memcpy(&p->peer, &peer, sizeof(peer));
        memcpy((char*)(p + 1), username, strlen(username) + 1);

        // server forward and request punch
        sockaddr_in serverAddr = { 0 };
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = udp_server_ip;
        serverAddr.sin_port = htons(UDP_SERVER_PORT);
        sendto(udp_fd, (char*)p, sizeof(Message) + MAX_USERNAME, 0,
            (sockaddr*)&serverAddr, sizeof(serverAddr));
        
        // send to target directly
        sockaddr_in peerAddr = { 0 };
        peerAddr.sin_family = AF_INET;
        peerAddr.sin_addr.s_addr = peerInfo->addr[peerInfo->addrNum-1].ip;
        peerAddr.sin_port = htons(peerInfo->addr[peerInfo->addrNum-1].port);
        printf("directly p2p msg to %s:%d \n", inet_ntoa(peerAddr.sin_addr), ntohs(peerAddr.sin_port));
        sendto(udp_fd, (char*)p, sizeof(p), 0,
            (sockaddr*)&peerAddr, sizeof(peerAddr));
        
        // wait for RequestAck
        for (int j = 0; j < 10; j++)
        {   
            if (peerInfo->p2pAddr.ip != 0) 
            {
                break;
            }
            usleep(300000);
        }
    }
    return 0;
}

void* UDPClient::RecvMsg(void* udpclient)
{
    UDPClient* that = (UDPClient*) udpclient;
    while (1) {
        struct sockaddr_in fromaddr;
        bzero(&fromaddr, sizeof(fromaddr));
        socklen_t fromaddr_len = sizeof(sockaddr);

        int recv_len = recvfrom(that->udp_fd, that->udpRecv, MAX_PACKET_SIZE, 0,
                (struct sockaddr*)&fromaddr, &fromaddr_len);

        if (recv < 0)
        {
            that->Error("Recv msg error: ");
        }

        that->HandleRecvMsg((sockaddr*)&fromaddr, fromaddr_len);
    }
    pthread_exit(NULL);
}

void UDPClient::HandleRecvMsg(sockaddr* fromaddr, int addr_len)
{
    Message* msg = (Message*)udpRecv;
    printf("msg->type: %d\n", msg->type);
    if (sizeof(udpRecv) < sizeof(msg)) { return; }

    switch(msg->type)
    {
        case USERLOGACK: // recv login ack from server
            {
                memcpy(&peer, &msg->peer, sizeof(PEER_INFO));
                login = true;
            }
            break;

        case P2PMESSAGE: // msg from other peer node
            {
                printf("recv p2p msg from %s: %s\n", msg->peer.username, msg->text);

                // send ack
                Message ackMsg;
                ackMsg.type = P2PMESSAGEACK;
                memcpy(&ackMsg.peer, &peer, sizeof(PEER_INFO));
                sendto(udp_fd, (char*)&ackMsg, sizeof(ackMsg), 0, fromaddr, addr_len);
            }
            break;

        case P2PMESSAGEACK:
            {
                msgAck = true;
            }
            break;
        
        case REQUESTPUNCH: // punch request from server or other peer node
            {
                Message ackMsg;
                ackMsg.type = REQUESTPUNCHACK;
                memcpy(&ackMsg.peer, &peer, sizeof(PEER_INFO));

                if (((sockaddr_in*)fromaddr)->sin_addr.s_addr != udp_server_ip) // from peer node
                {
                    // printf("request from peer;\n");
                    // pthread_mutex_lock(&peerListLock);
                    // PEER_INFO* peerInfo = peerList->GetPeer(msg->peer.username);
                    // if (peerInfo != NULL)
                    // {
                    //     if (peerInfo->p2pAddr.ip == 0)
                    //     {
                    //         peerInfo->p2pAddr.ip = ((sockaddr_in*)fromaddr)->sin_addr.s_addr;
                    //         peerInfo->p2pAddr.port = ntohs(((sockaddr_in*)fromaddr)->sin_port);

                    //         printf("set p2p address for %s -> %s:%d", peerInfo->username,
                    //             inet_ntoa(((sockaddr_in*)fromaddr)->sin_addr), ntohs(((sockaddr_in*)fromaddr)->sin_port));
                    //     }
                    // }
                    // pthread_mutex_unlock(&peerListLock);

                    // sendto(udp_fd, (char*)&ackMsg, sizeof(Message), 0, fromaddr, sizeof(fromaddr));
                }
                else // server
                {
                    /* 1. B punch to A */
                    sockaddr_in peerAddr = { 0 };
                    peerAddr.sin_family = AF_INET;
                    
                    // @addrNum - 1: public address of peer
                    peerAddr.sin_addr.s_addr = msg->peer.addr[msg->peer.addrNum - 1].ip;
                    peerAddr.sin_port = htons(msg->peer.addr[msg->peer.addrNum - 1].port);
                    printf("request user: %s:%d\n", inet_ntoa(peerAddr.sin_addr), ntohs(peerAddr.sin_port));

                    ackMsg.type = 120;
                    sendto(udp_fd, (char*)&ackMsg, sizeof(Message), 0, (sockaddr*)&peerAddr, sizeof(peerAddr));

                    
                    /* 2. B tells S that there exists one hole from B to A, A could send msg to B */
                    ackMsg.type = REQUESTPUNCHACK;
                    sockaddr_in serverAddr = { 0 };
                    serverAddr.sin_family = AF_INET;
                    serverAddr.sin_addr.s_addr = udp_server_ip;
                    serverAddr.sin_port = htons(UDP_SERVER_PORT);
                    sendto(udp_fd, (char*)&ackMsg, sizeof(Message), 0,
                        (sockaddr*)&serverAddr, sizeof(serverAddr));
                }
            }
            break;
        
        case REQUESTPUNCHACK: // recv punch info from peer node, set p2p communication addr
            {
                pthread_mutex_lock(&peerListLock);
                PEER_INFO *peerInfo = peerList->GetPeer(msg->peer.username);
                pthread_mutex_unlock(&peerListLock);

                memcpy(&peerInfo->p2pAddr.ip, &msg->peer.addr[msg->peer.addrNum-1].ip, sizeof(msg->peer.addr[msg->peer.addrNum-1].ip));
                peerInfo->p2pAddr.port = msg->peer.addr[msg->peer.addrNum-1].port;
                
                printf("set p2p address for %s -> %s:%d \n", peer.username,
                    inet_ntoa(*((struct in_addr*)&peerInfo->p2pAddr.ip)), peerInfo->p2pAddr.port);
            }
            break;

        case USERACTIVEQUERY:
            {
                Message ackMsg;
                ackMsg.type = USERACTIVEQUERYACK;
                memcpy(&ackMsg.peer, &peer, sizeof(PEER_INFO));
                sendto(udp_fd, (char*)&ackMsg, sizeof(ackMsg), 0, fromaddr, sizeof(fromaddr));
            }
            break;
        
        case GETPEERLIST:
            {
                pthread_mutex_lock(&peerListLock);
                peerList->AddPeer(&msg->peer);
                pthread_mutex_unlock(&peerListLock);
            }
            break;
        
        case USERLISTCMP:
            {
                userList = true;
            }
            break;
    }
}
